<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace Hyperfx\ServiceGovernanceNacos\Process;

use Hyperfx\ServiceGovernanceNacos\Contract\PipeMessageInterface;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Process\AbstractProcess;
use Hyperf\Process\ProcessCollector;
use Hyperf\Process\ProcessManager;
use Hyperf\ServiceGovernance\DriverManager;
use Hyperf\Utils\Codec\Json;
use Hyperfx\ServiceGovernanceNacos\PipeMessage;
use Psr\Container\ContainerInterface;
use Swoole\Server;

class ServiceNodeSyncProcess extends AbstractProcess
{
    /**
     * @var string
     */
    public string $name = 'nacos-discovery-node-sync';

    /**
     * @var Server
     */
    protected $server;

    /**
     * @var StdoutLoggerInterface
     */
    protected $logger;

    /**
     * @var ConfigInterface
     */
    private $config;

    public function __construct(protected DriverManager $driverManager, protected ContainerInterface $container)
    {
        $this->logger = $container->get(StdoutLoggerInterface::class);
        $this->config = $this->container->get(ConfigInterface::class);
    }


    public function bind($server): void
    {
        $this->server = $server;
        parent::bind($server);
    }


    public function handle(): void
    {
        $providers = $this->config->get('services.providers', []);
        $nacosDriver = $this->driverManager->get('nacos');

        // 保证最小值为3秒
        $interval = (int) $this->config->get('services.drivers.nacos.discovery_interval', 3);
        if ($interval < 3) {
            $interval = 3;
        }

        $this->logger->debug(sprintf('Process#%s start running.', $this->name));

        foreach ($providers as $provider) {
            $this->_handle($nacosDriver, $provider);
        }

        while (ProcessManager::isRunning()) {
            sleep($interval);
            foreach ($providers as $provider) {
                $this->_handle($nacosDriver, $provider);
            }
        }
    }

    private function _handle($nacos, $provider) {

        $service = $provider['service'];
        $metadata = $provider['metadata'];

        $data = $nacos->getNodes('', $service, $metadata);
        if (empty($data)) {
            $this->logger->alert('No nodes available', [
                'service' => $service,
                'metadata' => Json::encode($metadata),
            ]);
            return [];
        }

        $message = new PipeMessage([$service, $data]);
        $this->shareMessageToWorkers($message);
        $this->shareMessageToUserProcesses($message);

        $this->logger->debug(sprintf('Process#%s Service discovery node updated successfully', $this->name), [
            'service' => $service,
            'nodes' => Json::encode($data)
        ]);
    }

    protected function shareMessageToWorkers(PipeMessageInterface $message): void
    {
        if ($this->server instanceof Server) {
            $workerCount = $this->server->setting['worker_num'] + ($this->server->setting['task_worker_num'] ?? 0) - 1;
            for ($workerId = 0; $workerId <= $workerCount; ++$workerId) {
                $this->server->sendMessage($message, $workerId);
            }
        }
    }

    protected function shareMessageToUserProcesses(PipeMessageInterface $message): void
    {
        $processes = ProcessCollector::all();
        if ($processes) {
            $string = serialize($message);
            /** @var \Swoole\Process $process */
            foreach ($processes as $process) {
                $result = $process->exportSocket()->send($string, 10);
                if ($result === false) {
                    $this->logger->error(sprintf('Service discovery node synchronization failed. Please restart the server. pid: %s', $process->pid));
                }
            }
        }
    }

    public function isEnable($server): bool
    {
        return $this->config->get('services.enable.discovery', true)
            && !empty($this->config->get('services.providers', []))
            && !empty($this->config->get('services.drivers.nacos', []));
    }
}